package com.lsa.helpers.tree;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang.ArrayUtils;

import com.lsa.helpers.Pair;

public class TgfTreeFactory
{
    private static String nodeSectionSplitRegex = "(.+)\\s+(.*)";
    private static Pattern nodeSectionSlitPattern = Pattern.compile(nodeSectionSplitRegex);
    
    private static String edgeSectionSplitRegex = "(.+)\\s+(.+)\\s*(.*)";
    private static Pattern edgeSectionSlitPattern = Pattern.compile(edgeSectionSplitRegex);
    
    private static String sectionRegex = ".*[#].*";
    private static Pattern sectionPattern = Pattern.compile(sectionRegex);
    
    private static String lineSeparator = System.getProperty("line.separator");
    
    public static ITree<Pair<String, Object>> createStringTreeFromArray(final int[] array){
    	if (ArrayUtils.isEmpty(array)) return new Tree<Pair<String, Object>>(null);
    	
    	class Index2NodeHolder{
    		Map<Integer, TreeNode<Pair<String, Object>>> index2Node = new HashMap<Integer, TreeNode<Pair<String,Object>>>();
    		
    		TreeNode<Pair<String, Object>> createTreeNode(int index, int val){
    	    	return new TreeNode<Pair<String, Object>>(new Pair<String, Object>(""+index, val));
    	    }

    		public TreeNode<Pair<String, Object>> getOrCreate(int i){
    			TreeNode<Pair<String, Object>> node;
    			if (!index2Node.containsKey(i)){
        			node = createTreeNode(i, array[i - 1]);
        			index2Node.put(i, node);
        		}
    			else{
    				node = index2Node.get(i);
    			}
    			return node;
    		}
    	}
    	
    	Index2NodeHolder index2NodeHolder = new Index2NodeHolder();
    	
    	ITreeNode<Pair<String, Object>> treeRoot = null;
    	
    	for (int i = 1; i <= array.length / 2; i++) {
    		TreeNode<Pair<String, Object>> node = index2NodeHolder.getOrCreate(i);
    		if (i == 1) treeRoot = node;
    		
    		TreeNode<Pair<String, Object>> leftNode = index2NodeHolder.getOrCreate(i * 2);
    		node.addChild(leftNode);
    		if (i * 2 + 1 <= array.length){
    			ITreeNode<Pair<String, Object>> rightNode = index2NodeHolder.getOrCreate(i * 2 + 1);
    			node.addChild(rightNode);
    		}
		}
    	
    	ITree<Pair<String, Object>> tree = new Tree<Pair<String, Object>>(treeRoot);
        return tree;
    }
    
    /*
     *    public static ITree<Pair<String, Object>> readStringTreeFromFile(String path) throws IOException{
        BufferedReader reader = new BufferedReader(new FileReader(new File(path)));
     * */
    
    public static ITree<Pair<String, Object>> readStringTreeFromFile(String path) throws IOException{
    	return readStringTreeFrom(new FileInputStream(new File(path)));
    }
    
    public static ITree<Pair<String, Object>> readStringTreeFrom(InputStream stream) throws IOException{
        BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
        String line = null;
        
        boolean isEdgesSection = false;
        Map<String, TreeNode<Pair<String, Object>>> id2TreeNode = new HashMap<String, TreeNode<Pair<String, Object>>>();
        NodeRefCounter nodeRefCounter = new NodeRefCounter();
        
        while(( line = reader.readLine()) != null){
            if (sectionPattern.matcher(line).matches()){
                isEdgesSection = true;
                continue;
            }
            else{
                if (!isEdgesSection){
                    Matcher matcher = nodeSectionSlitPattern.matcher(line);
                    if (matcher.find()){
                        String id = matcher.group(1);
                        String label = matcher.group(1);
                        TreeNode<Pair<String, Object>> treeNode = new TreeNode<Pair<String, Object>>(new Pair<String, Object>(id, label));
                        id2TreeNode.put(id, treeNode);
                    }    
                }
                else{
                    Matcher matcher = edgeSectionSlitPattern.matcher(line);
                    if (matcher.find()){
                        String id1 = matcher.group(1);
                        String id2 = matcher.group(2);
                        TreeNode<Pair<String, Object>> node1 = id2TreeNode.get(id1);
                        TreeNode<Pair<String, Object>> node2 = id2TreeNode.get(id2);
                        node1.addChild(node2);
                        
                        nodeRefCounter.countNode(id2);
                    }
                }
            }
        }
        
        ITreeNode<Pair<String, Object>> treeRoot = null;
        
        for( Map.Entry<String, TreeNode<Pair<String, Object>>> id2Node : id2TreeNode.entrySet() ){
            if (!nodeRefCounter.isHasReferences(id2Node.getKey())){
                treeRoot = id2Node.getValue();
                break;
            }
        }
        
        ITree<Pair<String, Object>> tree = new Tree<Pair<String, Object>>(treeRoot);
        return tree;
    }
    
    public static void saveStringTreeAsTgf(ITree<Pair<String, Object>> tree, String path) throws IOException{
    	saveStringTreeAsTgf(tree, null, path);
    }
    
    public static void saveStringTreeAsTgf(ITree<Pair<String, Object>> tree, final ITreeLabeling<Pair<String, Object>> labeling,  String path) throws IOException{
    	saveStringTreeAsTgf(tree.getRoot(), labeling, path);
    }
    
    public static void saveStringTreeAsTgf(ITreeNode<Pair<String, Object>> rootNode, final ITreeLabeling<Pair<String, Object>> labeling,  String path) throws IOException{
        final BufferedWriter writer = new BufferedWriter(new FileWriter(path));
        TreeWalker<Pair<String, Object>> treeWalker = new TreeWalker<Pair<String, Object>>(new ITreeVisitor<Pair<String, Object>>()
        {
            @Override
            public void visit(ITreeNode<Pair<String, Object>> node) throws Exception{
                Object label = labeling == null? node.getData().getSecond() : labeling.label(node);
				writer.write( node.getData().getFirst()+" " + label + lineSeparator);
            }
        });
        try
        {
            treeWalker.walk(rootNode);
            writer.write("#" + lineSeparator);
            treeWalker = new TreeWalker<Pair<String, Object>>(new ITreeVisitor<Pair<String, Object>>()
            {
                @Override
                public void visit(ITreeNode<Pair<String, Object>> node) throws Exception{
                    String id = node.getData().getFirst();
                    for( ITreeNode<Pair<String, Object>> childNode : node.getChildren() )
                    {
                    	if (childNode != null){
                    		writer.write(id+" " + childNode.getData().getFirst() + lineSeparator);
                    	}
                    }
                }
            });
            treeWalker.walk(rootNode);
        }
        catch( Exception e )
        {
            e.printStackTrace();
        }
        
        writer.close();
    }
    
    private static class NodeRefCounter{
        private Map<String, Integer> id2RefCount = new HashMap<String, Integer>();
        
        public void countNode(String id){
            Integer count = id2RefCount.get(id);
            if (count == null) count = 0;
            count++;
            id2RefCount.put(id, count);
        }
        
        public boolean isHasReferences(String id){
            Integer count = id2RefCount.get(id);
            if (count == null) count = 0;
            return count > 0;
        }
    }
}
